package helm

import (
	"errors"
	"fmt"
	"github.com/chartmuseum/helm-push/pkg/chartmuseum"
	helmClient "github.com/mittwald/go-helm-client"
	"github.com/walle/targz"
	"gluenet/internal/pkg/infra"
	"gopkg.in/yaml.v3"
	"helm.sh/helm/v3/pkg/action"
	"helm.sh/helm/v3/pkg/repo"
	"io"
	"io/ioutil"
	"os"
	"path/filepath"
	"strings"
	"time"
)

var DB *helmCli

type helmCli struct {
	localRepoUrl   string
	localRepoName  string
	localRepoCache string
	helmCli        helmClient.Client
	repoCli        *chartmuseum.Client
	uploadsCache   string
}

func (r *helmCli) ImortRepoChart(repo string, name string, versoin string, userId string) (*infra.HelmInfo, error) {
	if _, err := os.Stat(filepath.Join(r.localRepoCache, ".helmcache", repo+"-index.yaml")); err != nil {
		return nil, err
	}

	_, filePath, err := r.helmCli.GetChart(strings.Join([]string{repo, name}, "/"), &action.ChartPathOptions{Version: versoin})
	if err != nil {
		return nil, err
	}

	tmpCacheDir := filepath.Join(r.uploadsCache, userId)
	info, packageName, err := r.injectUser2(userId, filePath, tmpCacheDir)
	if err != nil {
		return nil, fmt.Errorf("inject user id %v", err)
	}

	// 上传包
	_, err = r.repoCli.UploadChartPackage(packageName, true)
	if err != nil {
		return nil, fmt.Errorf("uploads file %v", err)
	}

	// 删除缓存
	if err = os.Remove(filePath); err != nil {
		return nil, err
	}
	if err = os.Remove(packageName); err != nil {
		return nil, err
	}
	if err = os.RemoveAll(tmpCacheDir); err != nil {
		return nil, err
	}

	// 更新信息
	var tryTime = 10
	for {
		if tryTime < 0 {
			return nil, fmt.Errorf("can't import the upload file %v", info.Name)
		}
		if err = r.helmCli.UpdateChartRepos(); err != nil {
			return nil, err
		}
		res, err := r.QueryCharts3(info.Name, info.Version)
		if err != nil {
			if !errors.Is(err, infra.DataNotFind) {
				return nil, err
			}
			tryTime--
			time.Sleep(time.Microsecond * 100)
		} else {
			return res, nil
		}
	}
	return nil, fmt.Errorf("unkown error")
}

func (r *helmCli) ListRepoChart(repoName string, url string) (map[string][]infra.ChartInfoSimple, error) {
	if err := r.helmCli.AddOrUpdateChartRepo(repo.Entry{
		Name: repoName,
		URL:  url,
	}); err != nil {
		return nil, err
	}

	bts, _ := ioutil.ReadFile(filepath.Join(r.localRepoCache, ".helmcache", repoName+"-index.yaml"))
	var charts infra.ChartsIndex
	if err := yaml.Unmarshal(bts, &charts); err != nil {
		return nil, err
	}

	var simpleInfo = make(map[string][]infra.ChartInfoSimple, len(charts.Entries))
	for chartName, versions := range charts.Entries {
		infos := make([]infra.ChartInfoSimple, len(versions))
		for k, version := range versions {
			infos[k].Name = version.Name
			infos[k].Description = version.Description
			infos[k].Version = version.Version
		}
		simpleInfo[chartName] = infos
	}

	return simpleInfo, nil
}

func (r *helmCli) SaveChartToLocalRepo(userId string, fileName string, file io.Reader) (*infra.HelmInfo, error) {
	// 接受上传文件
	out, err := os.OpenFile(filepath.Join(r.uploadsCache, fileName), os.O_WRONLY|os.O_CREATE, 0666)
	if err != nil {
		return nil, fmt.Errorf("open save file %v", err)
	}
	_, err = io.Copy(out, file)
	if err != nil {
		return nil, fmt.Errorf("copy %v", err)
	}
	out.Close()

	// 注入用户
	tmpCacheDir := filepath.Join(r.uploadsCache, userId)
	filePath := filepath.Join(r.uploadsCache, fileName)
	info, packageName, err := r.injectUser2(userId, filePath, tmpCacheDir)
	if err != nil {
		return nil, fmt.Errorf("inject user id %v", err)
	}

	// 上传包
	_, err = r.repoCli.UploadChartPackage(packageName, true)
	if err != nil {
		return nil, fmt.Errorf("uploads file %v", err)
	}

	// 删除缓存
	if err = os.Remove(packageName); err != nil {
		return nil, err
	}
	if err = os.RemoveAll(tmpCacheDir); err != nil {
		return nil, err
	}

	// 更新信息
	var tryTime = 10
	for {
		if tryTime < 0 {
			return nil, fmt.Errorf("can't import the upload file %v", info.Name)
		}
		if err = r.helmCli.UpdateChartRepos(); err != nil {
			return nil, err
		}
		res, err := r.QueryCharts2(info.Name, info.Version)
		if err != nil {
			if !errors.Is(err, infra.DataNotFind) {
				return nil, err
			}
			tryTime--
			time.Sleep(time.Microsecond * 100)
		} else {
			return res, nil
		}
	}
	return nil, fmt.Errorf("unkown error")
}

func (r *helmCli) SaveCharts(userId string, fileName string, file io.Reader) (*infra.HelmInfo, error) {
	out, err := os.OpenFile(filepath.Join(r.uploadsCache, fileName), os.O_WRONLY|os.O_CREATE, 0666)
	if err != nil {
		return nil, fmt.Errorf("open save file %v", err)
	}
	_, err = io.Copy(out, file)
	if err != nil {
		return nil, fmt.Errorf("copy %v", err)
	}
	out.Close()

	tmpCacheDir := filepath.Join(r.uploadsCache, userId)
	infoI, err := r.injectUser(userId, fileName, tmpCacheDir)
	if err != nil {
		return nil, fmt.Errorf("inject user id %v", err)
	}
	info := infoI.([]string)
	newName, version, packageName := info[0], info[1], info[2]

	_, err = r.repoCli.UploadChartPackage(packageName, true)
	if err != nil {
		return nil, fmt.Errorf("uploads file %v", err)
	}

	if err = os.Remove(packageName); err != nil {
		return nil, err
	}
	if err = os.RemoveAll(tmpCacheDir); err != nil {
		return nil, err
	}

	var tryTime = 10
	//var chartName = strings.Split(fileName, ".")[0]
	for {
		if tryTime < 0 {
			return nil, fmt.Errorf("can't import the upload file %v", newName)
		}
		if err = r.helmCli.UpdateChartRepos(); err != nil {
			return nil, err
		}
		res, err := r.QueryCharts2(newName, version)
		if err != nil {
			if !errors.Is(err, infra.DataNotFind) {
				return nil, err
			}
			tryTime--
			time.Sleep(time.Microsecond * 100)
		} else {
			return res, nil
		}
	}
	return nil, fmt.Errorf("unkown error")
}

func (r *helmCli) injectUser2(userId, filePath, tmpCacheDir string) (*infra.ChartYaml, string, error) {
	// 解压
	if err := targz.Extract(filePath, tmpCacheDir); err != nil {
		return nil, "", fmt.Errorf("decompress file %v", err)
	}
	fs, err := ioutil.ReadDir(tmpCacheDir)
	if err != nil {
		return nil, "", fmt.Errorf("read decompress dir %v", err)
	}
	if len(fs) == 0 {
		return nil, "", fmt.Errorf("decompress file have no dir")
	}
	if len(fs) > 1 {
		return nil, "", fmt.Errorf("decompress file have much dir")
	}

	// 读chart
	chartDir := filepath.Join(tmpCacheDir, fs[0].Name())
	bts, _ := ioutil.ReadFile(filepath.Join(chartDir, "Chart.yaml"))
	var chart infra.ChartYaml
	if err = yaml.Unmarshal(bts, &chart); err != nil {
		return nil, "", fmt.Errorf("decode yaml %v", err)
	}

	// 改名
	chart.Name = strings.Join([]string{userId, chart.Name}, "-")

	// 写chart
	btsNew, _ := yaml.Marshal(chart)
	if err = ioutil.WriteFile(filepath.Join(chartDir, "Chart.yaml"), btsNew, 0777); err != nil {
		return nil, "", fmt.Errorf("modify file %v", err)
	}

	// 压缩
	client := action.NewPackage()
	packageName, err := client.Run(chartDir, nil)
	if err != nil {
		return nil, "", fmt.Errorf("pacakge file %v", err)
	}
	return &chart, packageName, err
}

func (r *helmCli) injectUser(userId, fileName, path string) (interface{}, error) {
	if err := targz.Extract(filepath.Join(r.uploadsCache, fileName), path); err != nil {
		return nil, fmt.Errorf("decompress file %v", err)
	}

	fs, err := ioutil.ReadDir(path)
	if err != nil {
		return nil, fmt.Errorf("read decompress dir %v", err)
	}
	if len(fs) == 0 {
		return nil, fmt.Errorf("decompress file have no dir")
	}
	if len(fs) > 1 {
		return nil, fmt.Errorf("decompress file have much dir")
	}

	chartDir := filepath.Join(path, fs[0].Name())

	bts, _ := ioutil.ReadFile(filepath.Join(chartDir, "Chart.yaml"))
	var chart interface{}
	if err = yaml.Unmarshal(bts, &chart); err != nil {
		return nil, fmt.Errorf("decode yaml %v", err)
	}
	charts, ok := chart.(map[string]interface{})
	if !ok {
		return nil, fmt.Errorf("Chart.yaml is not yaml format")
	}
	name, ok := charts["name"]
	if !ok {
		return nil, fmt.Errorf("Chart.yaml don't contain name")
	}
	version, ok := charts["version"]
	if !ok {
		return nil, fmt.Errorf("Chart.yaml don't contain name")
	}
	newName := strings.Join([]string{userId, name.(string)}, "-")
	chart.(map[string]interface{})["name"] = newName
	btsNew, _ := yaml.Marshal(chart)
	if err = ioutil.WriteFile(filepath.Join(chartDir, "Chart.yaml"), btsNew, 0777); err != nil {
		return nil, fmt.Errorf("modify file %v", err)
	}

	client := action.NewPackage()
	packageName, err := client.Run(chartDir, nil)
	if err != nil {
		return nil, fmt.Errorf("pacakge file %v", err)
	}
	return []string{newName, version.(string), packageName}, err
}

func (r *helmCli) QueryCharts3(name string, version string) (*infra.HelmInfo, error) {
	bts, _ := ioutil.ReadFile(filepath.Join(r.localRepoCache, ".helmcache", r.localRepoName+"-charts.txt"))
	charts := strings.Split(string(bts), "\n")
	match := false
	for _, v := range charts {
		if v == name {
			match = true
		}
	}
	if !match {
		return nil, infra.DataNotFind
	}

	btsIndex, _ := ioutil.ReadFile(filepath.Join(r.localRepoCache, ".helmcache", r.localRepoName+"-index.yaml"))
	var indexI infra.ChartsIndex
	if err := yaml.Unmarshal(btsIndex, &indexI); err != nil {
		return nil, err
	}

	matchVersion := false
	info := &infra.HelmInfo{}
	for _, chart := range indexI.Entries[name] {
		if chart.Version == version {
			matchVersion = true
			info.Version = version
			info.Name = chart.Name
			info.Describe = chart.Description
			info.Detail = chart
			break
		}
	}

	if !matchVersion {
		return nil, infra.DataNotFind
	}
	return info, nil
}

func (r *helmCli) QueryCharts2(name string, version string) (*infra.HelmInfo, error) {
	bts, _ := ioutil.ReadFile(filepath.Join(r.localRepoCache, ".helmcache", r.localRepoName+"-charts.txt"))
	charts := strings.Split(string(bts), "\n")
	match := false
	for _, v := range charts {
		if v == name {
			match = true
		}
	}
	if !match {
		return nil, infra.DataNotFind
	}

	btsIndex, _ := ioutil.ReadFile(filepath.Join(r.localRepoCache, ".helmcache", r.localRepoName+"-index.yaml"))
	var indexI interface{}
	if err := yaml.Unmarshal(btsIndex, &indexI); err != nil {
		return nil, err
	}
	index, ok := indexI.(map[string]interface{})
	if !ok {
		return nil, fmt.Errorf("indexI is not map[string]interface{}")
	}
	entries, ok := index["entries"]
	if !ok {
		return nil, fmt.Errorf("index not contain entries")
	}
	chartsMap, ok := entries.(map[string]interface{})
	if !ok {
		return nil, fmt.Errorf("entries is not map[string]interface{}")
	}

	chartsListI, ok := chartsMap[name]
	if !ok {
		return nil, fmt.Errorf("chartsmap not contain test-uploads")
	}

	chartsList, ok := chartsListI.([]interface{})
	if !ok {
		return nil, fmt.Errorf("charts list is not list")
	}

	matchVersion := false
	info := &infra.HelmInfo{}
	for _, chartI := range chartsList {
		chart, ok := chartI.(map[string]interface{})
		if !ok {
			return nil, fmt.Errorf("chart is not map[string]interface{}")
		}
		v, ok := chart["version"]
		if !ok {
			return nil, fmt.Errorf("charts spec not contian version")
		}
		n, ok := chart["name"]
		if !ok {
			return nil, fmt.Errorf("charts spec not contian name")
		}
		describe, ok := chart["description"]
		if !ok {
			return nil, fmt.Errorf("charts spec not contian describe")
		}
		if v == version {
			matchVersion = true
			info.Version = version
			info.Name = n.(string)
			info.Describe = describe.(string)
			info.Detail = chart
			break
		}
	}

	if !matchVersion {
		return nil, infra.DataNotFind
	}
	return info, nil
}

//func (r *helmCli) QueryCharts(name string) ([]*HelmInfo, error) {
//	idx, err := BuildIndex(r.localRepoCache+".helmrepo", r.localRepoCache+".helmcache")
//	if err != nil {
//		return nil, err
//	}
//
//	var res []*Result
//
//	if len(name) == 0 {
//		// list all
//		res = idx.All()
//	} else {
//		// get some
//		res, err = idx.Search(name, 25, false)
//		if err != nil {
//			return nil, err
//		}
//	}
//	SortScore(res)
//
//	var infos = make([]*HelmInfo, len(res))
//	for k, v := range res {
//		infos[k] = &HelmInfo{
//			Name:     v.Chart.Name,
//			Version:  v.Chart.Version,
//			Describe: v.Chart.Description,
//			//RepoKey:  v.Name,
//			Detail: v,
//		}
//	}
//
//	return infos, nil
//}

//func (r *helmCli) UpdateRepo() error {
//	return r.helmCli.UpdateChartRepos()
//}

func Install() error {
	repoName := "gluerepo"
	repoUrl := "http://129.226.101.5:30028"
	cacheDir := "C:\\WorkPlace\\Code\\gluenet\\build"

	mode := os.Getenv("MODE")
	if mode == "prod" {
		repoName = "gluerepo"
		cacheDir = "/tmp/"
		repoUrl = os.Getenv("REPO_URL")
	}

	repoCli, err := chartmuseum.NewClient(chartmuseum.URL(repoUrl))
	if err != nil {
		return err
	}

	opt := &helmClient.Options{
		Namespace:        "default", // Change this to the namespace you wish the client to operate in.
		RepositoryCache:  filepath.Join(cacheDir, ".helmcache"),
		RepositoryConfig: filepath.Join(cacheDir, ".helmrepo"),
		Debug:            true,
		Linting:          true,
		DebugLog:         func(format string, v ...interface{}) {},
	}

	helmClient, err := helmClient.New(opt)
	if err != nil {
		return err
	}

	err = helmClient.AddOrUpdateChartRepo(repo.Entry{
		Name: repoName,
		URL:  repoUrl,
	})
	if err != nil {
		return err
	}

	if err = helmClient.UpdateChartRepos(); err != nil {
		return fmt.Errorf("init update repo %v", err)
	}

	if err = os.Mkdir(filepath.Join(cacheDir, "uploads"), os.ModePerm); err != nil {
		if !errors.Is(err, os.ErrExist) {
			return err
		}
	}

	var db = &helmCli{
		localRepoCache: cacheDir,
		localRepoUrl:   repoUrl,
		localRepoName:  repoName,
		uploadsCache:   filepath.Join(cacheDir, "uploads"),
		//uploadsCache: "./uploads",
		repoCli: repoCli,
		helmCli: helmClient,
	}
	DB = db
	return nil
}
